﻿// (c) Copyright slimCODE Software Inc. - www.slimcode.com
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SlimCode.Utils;
using System.IO.Compression;
using System.Xml;
using System.Security.Cryptography;

namespace SlimCode.SlimPasswords
{
  class PasswordStore
  {
    private List<PasswordItem> m_passwords = new List<PasswordItem>();
    private List<ItemStrength> m_strengths = new List<ItemStrength>();

    public PasswordStore()
    {
    }

    public PasswordStore( string fileName, string password )
    {
      if( fileName == null )
        throw new ArgumentNullException( "fileName" );

      if( fileName.Length == 0 )
        throw new ArgumentException( "The fileName cannot be empty.", "fileName" );

      if( password == null )
        throw new ArgumentNullException( "password" );

      if( password.Length == 0 ) 
        throw new ArgumentException( "The password cannot be empty.", "password" );

      m_fileName = fileName;
      m_mainPassword = password;
    }

    #region FileName PROPERTY

    private string m_fileName = string.Empty;

    public string FileName
    {
      get { return m_fileName; }
    }

    #endregion

    #region MainPassword PROPERTY

    private string m_mainPassword = string.Empty;

    internal string MainPassword
    {
      get { return m_mainPassword; }
    }

    #endregion

    public IEnumerable<ItemStrength> Strengths
    {
      get { return m_strengths; }
    }

    public IEnumerable<PasswordItem> Passwords
    {
      get { return m_passwords; }
    }

    public void AddPasswordItem( PasswordItem item )
    {
      m_passwords.Add( item );
      m_strengths.Add( new ItemStrength( item ) );
    }

    public void DeletePasswordItem( PasswordItem item )
    {
      if( m_passwords.Remove( item ) )
      {
        ItemStrength foundStrength = m_strengths.Find( delegate( ItemStrength itemStrength )
          {
            return ( itemStrength.Item == item );
          } );

        if( foundStrength != null )
        {
          m_strengths.Remove( foundStrength );
        }
      }
    }

    public void UpdateStrengths( string command )
    {
      if( string.IsNullOrEmpty( command ) )
      {
        m_strengths.Sort( delegate( ItemStrength first, ItemStrength second )
          {
            return string.Compare( first.Item.Name, second.Item.Name, true );
          } );
      }
      else
      {
        foreach( ItemStrength item in m_strengths )
        {
          item.UpdateStrength( command );
        }

        m_strengths.Sort();
      }
    }

    public void Load( string fileName, string password )
    {
      try
      {
        using( FileStream fileStream = new FileStream( fileName, FileMode.Open ) )
        {
          using( AesStream aesStream = new AesStream( fileStream, password, 256, false ) )
          {
            using( DeflateStream deflateStream = new DeflateStream( aesStream, CompressionMode.Decompress ) )
            {
              XmlDocument document = new XmlDocument();

              document.Load( deflateStream );

              XmlNodeList itemNodes = document.SelectNodes( "//PasswordStore/Items/Item" );

              m_passwords.Clear();
              m_strengths.Clear();

              foreach( XmlNode itemNode in itemNodes )
              {
                try
                {
                  PasswordItem item = new PasswordItem( this );

                  if( item.Load( itemNode ) )
                  {
                    m_passwords.Add( item );
                    m_strengths.Add( new ItemStrength( item ) );
                  }
                }
                catch( Exception except )
                {
                  // Skip this item.
                  System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Load: Loading an item threw an error." );
                  System.Diagnostics.Trace.WriteLine( except.Message );
                }
              }

              // Remember filename and password on successful load.
              m_fileName = fileName;
              m_mainPassword = password;
            }
          }
        }
      }
      catch( CryptographicException except )
      {
        System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Load: CryptographicException." );
        System.Diagnostics.Trace.WriteLine( except.Message );

        throw new ApplicationException( "Could not decrypt the password store file. The password is invalid.", except );
      }
      catch( FileNotFoundException except )
      {
        System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Load: FileNotFoundException." );
        System.Diagnostics.Trace.WriteLine( except.Message );

        throw new ApplicationException( "The specified password store file does not exist.", except );
      }
      catch( XmlException except )
      {
        System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Load: XmlException." );
        System.Diagnostics.Trace.WriteLine( except.Message );

        throw new ApplicationException( "The specified file does not seem to be a valid slimKEYS password store file.", except );
      }
      catch( Exception except )
      {
        System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Load: Loading and decrypting the password store threw an error." );
        System.Diagnostics.Trace.WriteLine( except.Message );

        throw new ApplicationException( "An unexpected error occurred reading your password store file.", except );
      }
    }

    public void Save( string fileName, string password )
    {
      try
      {
        using( FileStream fileStream = new FileStream( fileName, FileMode.Create ) )
        {
          using( AesStream encrypt = new AesStream( fileStream, password, 256, true ) )
          {
            using( DeflateStream deflate = new DeflateStream( encrypt, CompressionMode.Compress ) )
            {
              using( XmlWriter writer = XmlWriter.Create( deflate ) )
              {
                writer.WriteStartDocument();

                writer.WriteStartElement( "PasswordStore" );
                writer.WriteStartElement( "Items" );

                foreach( PasswordItem item in m_passwords )
                {
                  writer.WriteStartElement( "Item" );

                  item.Save( writer );

                  writer.WriteEndElement(); // </Item>
                }

                writer.WriteEndElement(); // </Items>
                writer.WriteEndElement(); // </PasswordStore>
              }
            }
          }
        }
      }
      catch( Exception except )
      {
        System.Diagnostics.Trace.WriteLine( "[EX] PasswordStore.Save: Encrypting and saving the password store threw an error." );
        System.Diagnostics.Trace.WriteLine( except.Message );

        throw new ApplicationException( "An unexpected error occurred writing your password store file.", except );
      }
    }
  }
}
